package moa.learners;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Enumeration;

import moa.MOAObject;
import moa.core.Measurement;
import moa.core.StringUtils;
import moa.options.FlagOption;
import moa.options.IntOption;

import moa.structure.Element;
import moa.structure.Sequence;
import weka.core.Attribute;
import weka.core.Capabilities;
import weka.core.FastVector;
import weka.core.Instance;
import weka.core.Instances;

public class GSPSequenceFrequenceMiner 
	extends AbstractLearner{
	
	public String getPurposeString() {
		return "GSP";
	}
		
	public IntOption dataSeqOption = new IntOption(
			"dataSeqID", 'q',
			"The attribute number representing the data sequence ID..", 0, 0,
			Integer.MAX_VALUE);
  	/** OPTIONS **/
	  
	  /** Whether the classifier is run in debug mode. */

	public FlagOption debugOption = new FlagOption("debug",
			'b', "If set to true, algorithm may output additional info to the console..");
	

	public IntOption minSupportOption = new IntOption("minSupport", 's',
			"Minimum support threshold.", 90, 0,
			100);

	public IntOption filterAttributesOption = new IntOption("filterAttribute", 'f',
			"The attribute numbers (eg 0, 1) used for result filtering; only sequences containing the specified attributes in each of their elements/itemsets will be output; -1 prints all.", -1, -1,
			Integer.MAX_VALUE);

	
	
	protected boolean m_Debug = false;
		/** DECLARATIONS **/
	
	  /** for serialization */
	  private static final long serialVersionUID = -4119691320812254676L;

	  /** the minimum support threshold */
	  protected double m_MinSupport; 

	  /** number indicating the attribute holding the data sequence ID */
	  protected int m_DataSeqID;

	  /** original sequential data set to be used for sequential patterns extraction */
	  protected Instances m_OriginalDataSet;
	  protected Instances data;
	  
	  /** all generated frequent sequences, i.e. sequential patterns */
	  protected FastVector m_AllSequentialPatterns;
	  
	  /** number of cycles performed until termination */
	  protected int m_Cycles;
	  
	  /** String indicating the starting time of an cycle. */
	  protected String m_CycleStart;
	  
	  /** String indicating the ending time of an cycle. */
	  protected String m_CycleEnd;
	  
	  /** String indicating the starting time of the algorithm. */
	  protected String m_AlgorithmStart;
	  
	  /** String containing the attribute numbers that are used for result 
	   * filtering; -1 means no filtering */
	  protected String m_FilterAttributes;
	  
	  /** Vector containing the attribute numbers that are used for result 
	   * filtering; -1 means no filtering */
	  protected FastVector m_FilterAttrVector;
	  
	  public int nb_seq ;
	
		/** GSP TRAITMENT **/

	  /** 
	   * Returns the Capabilities of this associator. Maximally permissive
	   * capabilities are allowed by default. Derived associators should
	   * override this method and first disable all capabilities and then
	   * enable just those capabilities that make sense for the scheme.
	   *
	   * @return            the capabilities of this object
	   * @see               Capabilities
	   */
	  public Capabilities getCapabilities() {
	    Capabilities defaultC = new Capabilities(null);
	    defaultC.enableAll();
	    
	    return defaultC;
	  }

	  /** Get whether debugging is turned on.
	   *
	   * @return 			true if debugging output is on
	   */
	  public boolean getDebug() {
	    return m_Debug;
	  }
	
	  /**
	   * Extracts all sequential patterns out of a given sequential data set and 
	   * prints out the results.
	   * 
	   * @param data 	the original data set
	   */
	  public void buildAssociations(Instances data) throws Exception {
	    // can associator handle the data?
	    getCapabilities().testWithFail(data);

	    m_AllSequentialPatterns = new FastVector();
	    m_Cycles                = 0;
	    m_FilterAttrVector      = new FastVector();
	    m_AlgorithmStart        = getTimeAndDate();
	    m_OriginalDataSet       = new Instances(data);
	    
	    extractFilterAttributes(m_FilterAttributes);
	    findFrequentSequences();
	  }

	  /**
	   * Calculates the total number of extracted frequent sequences.
	   * 
	   * @return 			the total number of frequent sequences
	   */
	  protected int calcFreqSequencesTotal() {
	    int total = 0;
	    Enumeration<?> allSeqPatternsEnum = m_AllSequentialPatterns.elements();

	    while (allSeqPatternsEnum.hasMoreElements()) {
	      FastVector kSequences = (FastVector) allSeqPatternsEnum.nextElement();
	      total += kSequences.size();
	    }

	    return total;
	  }

	  /**
	   * Extracts the data sequences out of the original data set according to 
	   * their sequence id attribute, which is removed after extraction.
	   * 
	   * @param originalDataSet 	the original data set
	   * @param dataSeqID		the squence ID to use
	   * @return 			set of distinct data sequences
	   */
	  protected FastVector extractDataSequences (Instances originalDataSet, int dataSeqID) {
	    FastVector dataSequences = new FastVector();
	    int firstInstance = 0;
	    int lastInstance = 0;
	    Attribute seqIDAttribute = originalDataSet.attribute(dataSeqID);
	    nb_seq = seqIDAttribute.numValues();
	    System.out.println(".." + seqIDAttribute.numValues() );
	    for (int i = 0; i < seqIDAttribute.numValues(); i++) {
	    	
	      double sequenceID = originalDataSet.instance(firstInstance).value(dataSeqID);
	      while (lastInstance < originalDataSet.numInstances()
		  && sequenceID == originalDataSet.instance(lastInstance).value(dataSeqID)) {
		lastInstance++;
	      }
	      Instances dataSequence = new Instances(originalDataSet, firstInstance, (lastInstance)-firstInstance);
	      dataSequence.deleteAttributeAt(dataSeqID);
	      System.out.println();
	      dataSequences.addElement(dataSequence);
	      firstInstance = lastInstance;
	    }
	    return dataSequences;
	  }

	  /**
	   * Parses a given String containing attribute numbers which are used for 
	   * result filtering.
	   * 
	   * @param attrNumbers 	the String of attribute numbers
	   */
	  public void extractFilterAttributes(String attrNumbers) {
	    String numbers = attrNumbers.trim();

	    while (!numbers.equals("")) {
	      int commaLoc = numbers.indexOf(',');

	      if (commaLoc != -1) {
		String number = numbers.substring(0, commaLoc);
		numbers = numbers.substring(commaLoc + 1).trim();
		m_FilterAttrVector.addElement(Integer.decode(number));
	      } else {
		m_FilterAttrVector.addElement(Integer.decode(numbers));
		break;
	      }
	    }
	  }

	  /**
	   * The actual method for extracting frequent sequences.
	   * 
	   * @throws CloneNotSupportedException
	   */
	  protected void findFrequentSequences() throws CloneNotSupportedException {
	    m_CycleStart = getTimeAndDate();
	    Instances originalDataSet = m_OriginalDataSet;
	    FastVector dataSequences = extractDataSequences(m_OriginalDataSet, m_DataSeqID);
	    long minSupportCount = Math.round(m_MinSupport * dataSequences.size());
	    FastVector kMinusOneSequences;
	    FastVector kSequences;

	    originalDataSet.deleteAttributeAt(0);
	    FastVector oneElements = Element.getOneElements(originalDataSet);
	    m_Cycles = 1;

	    kSequences = Sequence.oneElementsToSequences(oneElements);
	    Sequence.updateSupportCount(kSequences, dataSequences);
	    kSequences = Sequence.deleteInfrequentSequences(kSequences, minSupportCount);

	    m_CycleEnd = getTimeAndDate();

	    if (kSequences.size() == 0) {
	      return;
	    }
	    while (kSequences.size() > 0) {
	      m_CycleStart = getTimeAndDate();

	      m_AllSequentialPatterns.addElement(kSequences.copy());
	      kMinusOneSequences = kSequences;
	      kSequences = Sequence.aprioriGen(kMinusOneSequences);
	      Sequence.updateSupportCount(kSequences, dataSequences);
	      kSequences = Sequence.deleteInfrequentSequences(kSequences, minSupportCount);

	      m_CycleEnd = getTimeAndDate();
	      
	      if (getDebug())
		System.out.println(
		    "Cycle " + m_Cycles + " from " + m_CycleStart + " to " + m_CycleEnd);
	      
	      m_Cycles++;
	    }
	  }
	  
	  /**
	   * Returns the current time and date.
	   * 
	   * @return 			the time and date
	   */
	  protected String getTimeAndDate() {
	    SimpleDateFormat	dateFormat;
	    
	    dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
	    
	    return dateFormat.format(new Date());
	  }

	  /**
	   * Returns the time/date string the algorithm was started
	   * 
	   * @return			the time and date the algorithm was started
	   */
	  public String getAlgorithmStart() {
	    return m_AlgorithmStart;
	  }

	  /**
	   * Returns the time/date string the cycle was started
	   * 
	   * @return			the time and date the cycle was started
	   */
	  public String getCycleStart() {
	    return m_CycleStart;
	  }

	  /**
	   * Returns the time/date string the cycle ended
	   * 
	   * @return			the time and date the cycle ended
	   */
	  public String getCycleEnd() {
	    return m_CycleEnd;
	  }	  
	
		/** FUNCTION TO IMPLEMENT **/
	
	  
	@Override
	public void getModelDescription(StringBuilder out, int indent) {
		try {
			buildAssociations(data);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		StringUtils.appendIndented(out, indent, "GSP description ");
	    out.append("GeneralizedSequentialPatterns\n");
	    out.append("=============================\n\n"); 
	    out.append("Number of cycles performed: " + (m_Cycles-1) + "\n");
	    out.append("Total number of frequent sequences: " + calcFreqSequencesTotal() + "\n\n");
	    out.append("Frequent Sequences Details (filtered):\n\n");
	    for (int i = 0; i < m_AllSequentialPatterns.size(); i++) {
	      out.append("- " + (i+1) + "-sequences\n\n");
	      FastVector kSequences = (FastVector) m_AllSequentialPatterns.elementAt(i);
	      out.append(Sequence.setOfSequencesToString(kSequences, m_OriginalDataSet, m_FilterAttrVector,nb_seq) + "\n");
	    }
	    StringUtils.appendNewline(out);
		
	}

	@Override
	protected Measurement[] getModelMeasurementsImpl() {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public void resetLearningImpl() {
	    m_MinSupport       = 0.9;
	    m_DataSeqID        = 0;
	    m_FilterAttributes = "-1";
	    m_DataSeqID  = this.dataSeqOption.getValue();
	    m_MinSupport       = this.minSupportOption.getValue() * 0.01;
	    //m_FilterAttributes = this.filterAttributesOption.getValue();

	}
	
	protected int numberInstances;

	public void trainOnInstanceImpl(Instance inst) {
	    if (numberInstances == 0) {
	                data = new Instances(inst.dataset());
	     }
	   this.numberInstances++;
	   data.add(inst);
	}

	@Override
	public MOAObject getModel() {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public boolean isRandomizable() {
		// TODO Auto-generated method stub
		return false;
	}
	
	  /**
	   * Main method.
	   * 
	   * @param args 	commandline options, use -h for help
	   */
	  public static void main(String[] args) {

	  }



}
